/**
 * 
 */
package com.dalonedrau.jogl.opengl.ui;

import java.awt.Color;
import java.awt.Dimension;
import java.util.StringTokenizer;

import com.dalonedrau.jogl.engine.GlobalConsts;
import com.dalonedrau.jogl.engine.JOGLErrorHandler;
import com.dalonedrau.jogl.engine.Render;
import com.dalonedrau.jogl.engine.Sprite;
import com.dalonedrau.jogl.opengl.GameFont;
import com.dalonedrau.jogl.opengl.sprite.FontsheetObject;
import com.dalonedrau.jogl.opengl.sprite.GroupOfText;
import com.dalonedrau.jogl.opengl.ui.border.GuiBorder;
import com.dalonedrau.pool.PooledArrayList;
import com.dalonedrau.pool.PooledStringBuilder;
import com.dalonedrau.pool.StringBuilderPool;
import com.dalonedrau.pool.StringListPool;

/**
 * @author Donald
 */
public abstract class GuiTextItemImpl extends GuiComponentImpl
		implements GuiTextItem {
	/** the text color. */
	private Color		color			= Color.WHITE;
	/** the reference id of the {@link GameFont} used. */
	private int			fontRefId;
	/** the component's id. */
	private int			id;
	/** the component's text color when it is inactive. */
	private Color		inactiveColor;
	private int			maximumHeight;
	private int			maximumWidth;
	private int			minimumHeight;
	private int			minimumWidth;
	/** the preferred height. */
	private int			preferredHeight;
	/** the <code>GuiTextItemImpl</code>'s preferred size. */
	private Dimension	preferredSize	= new Dimension(0, 0);
	/** the preferred width. */
	private int			preferredWidth;
	private String[]	stringArr;
	/** the text displayed. */
	private byte[]		text			= new byte[0];
	/** the group of sprites set to render the text. */
	private GroupOfText	textGroup;
	/**
	 * Creates a new instance of GuiTextItemImpl.java.
	 * @param fontName the {@link GameFont} used
	 * @param newText the text displayed
	 */
	protected GuiTextItemImpl(final String fontName,
			final String newText) {
		super.setHorizontalAlignment(GuiComponent.LEFT);
		try {
			if (fontName != null
					&& newText != null) {
				fontRefId = GameFont.getInstance().getFontReferenceId(fontName);
				text = newText.getBytes();
				textGroup = new GroupOfText();
				textGroup.setFontRefId(fontRefId);
				id = GuiComponentLibrary.getInstance().getNextId();
				textGroup.setParentRefId(id);
			}
		} catch (Exception ex) {
			JOGLErrorHandler.getInstance().fatalError(ex);
		}
		GuiComponentLibrary.getInstance().addComponent(this);
	}
	/** Clears all lines. */
	public final void clearLines() {
		textGroup.clearLines();
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public final Color getColor() {
		return color;
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public final int getFontRefId() {
		return fontRefId;
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public int getId() {
		return id;
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public final Color getInactiveColor() {
		return inactiveColor;
	}
	/**
	 * Gets the maximumHeight
	 * @return {@link int}
	 */
	public int getMaximumHeight() {
		return maximumHeight;
	}
	/**
	 * Gets the maximumWidth
	 * @return {@link int}
	 */
	public int getMaximumWidth() {
		return maximumWidth;
	}
	/**
	 * Gets the minimumHeight
	 * @return {@link int}
	 */
	public int getMinimumHeight() {
		return minimumHeight;
	}
	/**
	 * Gets the minimumWidth
	 * @return {@link int}
	 */
	public int getMinimumWidth() {
		return minimumWidth;
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public int getNumberOfLinesOfText() {
		int val = 0;
		if (textGroup != null) {
			val = textGroup.getNumberOfLines();
		}
		return val;
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public Dimension getPreferredSize() {
		if (preferredSize.width == 0
				|| preferredSize.height == 0) {
			// ***************************************
			// ** CALCULATE THE SIZE OF THE UI WIDGET
			// ***************************************
			int pWidth = getPreferredWidth();
			// remember if the preferred width was ever set
			boolean prefWidthSet = true;
			if (pWidth == 0) {
				pWidth = GlobalConsts.SCREEN_WIDTH;
				prefWidthSet = false;
			}
			int w = 0, h = 0;
			// add border insets to width and height
			if (super.getBorder() != null) {
				w = super.getBorder().getInsets().left;
				w += super.getBorder().getInsets().right;
				h = super.getBorder().getInsets().top;
				h += super.getBorder().getInsets().bottom;
				pWidth -= super.getBorder().getInsets().left;
				pWidth -= super.getBorder().getInsets().right;
			}
			// remove insets from preferred width before sizing text
			pWidth -= super.getInsets().left;
			pWidth -= super.getInsets().right;
			// add label insets to width and height
			w += super.getInsets().left + super.getInsets().right;
			h += super.getInsets().top + super.getInsets().bottom;
			if (super.getSprite() != null) {
				// if this is an image label,
				// add image size to label width and height
				w += super.getSprite().getWidth();
				h += super.getSprite().getHeight();
			} else if (textGroup != null
					&& text.length > 0) {
				// this is a text label
				try {
					// ***************************************
					// ** ADD TEXT TO THE SPRITE GROUP
					// ***************************************
					sizeText();
					if (textGroup != null
							&& text != null
							&& text.length > 0) {
						Sprite.getInstance().computeFullStats(textGroup);
					}
				} catch (Exception ex) {
					JOGLErrorHandler.getInstance().fatalError(ex);
				}
				if (this instanceof SimpleScrollingTextWindow) {
					w += ((SimpleScrollingTextWindow) this).getTextWindowWidth();
					h += ((SimpleScrollingTextWindow) this).getTextWindowHeight();
				} else {
					w += getTextWidth();
					h += getTextHeight();
				}
			}
			pWidth = w;
			if (super.getBorder() != null) {
				pWidth -= super.getBorder().getInsets().left;
				pWidth -= super.getBorder().getInsets().right;
			}
			pWidth -= super.getInsets().left;
			pWidth -= super.getInsets().right;
			if (prefWidthSet) {
				w = getPreferredWidth();
			}
			if (minimumHeight > 0) {
				h = Math.max(h, minimumHeight);
			}
			if (maximumHeight > 0) {
				h = Math.min(h, maximumHeight);
			}
			if (minimumWidth > 0) {
				w = Math.max(w, minimumWidth);
			}
			if (maximumWidth > 0) {
				w = Math.min(w, maximumWidth);
			}
			preferredSize.width = w;
			preferredSize.height = h;
			if (super.getBorder() != null) {
				super.getBorder().setPreferredSize(w, h);
			}
			try {
				// ***************************************
				// ** POSITION ALL TEXT
				// ***************************************
				if (super.getPosition() != null) {
					if (super.getBorder() != null) {
						super.getBorder().setPosition(
								super.getPosition().getX(),
								super.getPosition().getY(),
								super.getPosition().getZ()
								);
					}
					if (super.getSprite() != null) {
						double x = super.getPosition().getX(), y = super
								.getPosition().getY(), z = super.getPosition()
								.getZ();
						if (super.getBorder() != null) {
							x += super.getBorder().getInsets().left;
							y += super.getBorder().getInsets().bottom;
						}
						x += super.getInsets().left;
						y += super.getInsets().bottom;
						super.getSprite().setPosition(x, y, z);
					}
				}
				positionText();
			} catch (Exception ex) {
				JOGLErrorHandler.getInstance().fatalError(ex);
			}
		}
		if (super.isDebug()) {
			System.out.println(super.getClass().getCanonicalName() 
					+ ".getPreferredSize(" + preferredSize);
		}
		return preferredSize;
	}
	/**
	 * Gets the preferredWidth.
	 * @return int
	 */
	public final int getPreferredWidth() {
		return preferredWidth;
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public final String getText() {
		return new String(text);
	}
	protected GroupOfText getTextGroup() {
		return textGroup;
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public float getTextHeight() {
		return textGroup.getFullHeight();
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public float getTextWidth() {
		return textGroup.getFullWidth();
	}
	/**
	 * Positions all text within the {@link GuiTextItem}'s boundaries.
	 * @throws Exception if an error occurs
	 */
	private void positionText() throws Exception {
		if (super.getPosition() != null
				&& textGroup != null
				&& text != null
				&& text.length > 0) {
			int height = getPreferredSize().height;
			// text position is set relative to component position
			// when component position changes, the text position doesn't need
			// to be re-calibrated
			double x = 0, y = 0;
			// move in from border insets
			if (super.getBorder() != null) {
				x += super.getBorder().getInsets().left;
				y += super.getBorder().getInsets().bottom;
				height -= super.getBorder().getInsets().top;
				height -= super.getBorder().getInsets().bottom;
			}
			// move up from label insets
			x += super.getInsets().left;
			y += super.getInsets().bottom;
			height -= super.getInsets().top;
			height -= super.getInsets().bottom;
			textGroup.setPosition(
					x,
					y,
					super.getPosition().getZ() + GlobalConsts.CHILD_STEP,
					height,
					super.getVerticalAlignment()
					);
		}
	}
	@Override
	public void prepareForRendering() throws Exception {
		super.prepareForRendering();
		if (super.isVisible()
				&& super.getPosition() != null) {
			if (textGroup != null) {
				if (super.isDebug()) {
					textGroup.setDebug(true);
				}
				Render.getInstance().registerEntity(textGroup);
			} else if (super.getSprite() != null) {
				Render.getInstance().registerEntity(super.getSprite());
			}
		}
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public final void setAlignment(final int hAlign, final int vAlign2) {
		super.setHorizontalAlignment(hAlign);
		super.setVerticalAlignment(vAlign2);
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void setBorder(final GuiBorder newBorder) {
		super.setBorder(newBorder);
		preferredWidth = 0;
		preferredSize.width = 0;
		preferredSize.height = 0;
		getPreferredSize();
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public final void setColor(final Color c) {
		color = c;
		float[] colors = c.getRGBComponents(null);
		textGroup.setRed(colors[0]);
		textGroup.setGreen(colors[1]);
		textGroup.setBlue(colors[2]);
		textGroup.setAlpha(colors[GlobalConsts.INT_003]);
		textGroup.setFloatStoreNumber(0);
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void setDebug(boolean val) {
		// TODO Auto-generated method stub
		super.setDebug(val);
		textGroup.setDebug(val);
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public final void setInactiveColor(final Color c) {
		inactiveColor = c;
	}
	/**
	 * Sets the maximum size.
	 * @param w the maximum width
	 * @param h the maximum height
	 */
	public void setMaximumSize(final int w, final int h) {
		maximumWidth = w;
		maximumHeight = h;
		preferredSize.width = 0;
		preferredSize.height = 0;
		getPreferredSize();
	}
	/**
	 * Sets the maximum width.
	 * @param w the maximum width
	 */
	public void setMaximumWidth(final int w) {
		maximumWidth = w;
		preferredSize.width = 0;
		preferredSize.height = 0;
		getPreferredSize();
	}
	/**
	 * Sets the minimum size.
	 * @param w the minimum width
	 * @param h the minimum height
	 */
	public void setMinimumSize(final int w, final int h) {
		minimumWidth = w;
		minimumHeight = h;
		preferredSize.width = 0;
		preferredSize.height = 0;
		getPreferredSize();
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void setPosition(final double x, final double y, final double z) {
		if (super.isDebug()) {
			System.out.println(super.getClass().getCanonicalName() 
					+ ".setPosition(" + x + "," + y);
		}
		boolean mustPositionText = false;
		if (super.getPosition() == null
				|| super.getPosition() != null
						&& super.getPosition().getX() == -999
						&& super.getPosition().getY() == -999
						&& super.getPosition().getZ() == -999) {
			mustPositionText = true;
		}
		if (!mustPositionText
				&& textGroup.getPosition() == null) {
			mustPositionText = true;
		}
		if (!mustPositionText
				&& !super.getPosition().equals(x, y, z)
				&& textGroup != null) {
			textGroup.setFloatStoreNumber(0);
		}
		super.setPosition(x, y, z);
		if (mustPositionText) {
			try {
				positionText();
			} catch (Exception ex) {
				JOGLErrorHandler.getInstance().fatalError(ex);
			}
		} else {
			textGroup.getPosition().setZ(z + GlobalConsts.CHILD_STEP);
		}
	}
	@Override
	public void setPositionExcludingBorders(final double x, final double y,
			final double z) {
		super.setPositionExcludingBorders(x, y, z);
		if (textGroup != null) {
			textGroup.setPosition(
					x,
					y,
					z + GlobalConsts.CHILD_STEP
					);
		}
	}
	public void setPositionExcludingBordersAndText(final double x,
			final double y,
			final double z) {
		super.setPositionExcludingBorders(x, y, z);
	}
	public void setPositionExclusive(final double x, final double y,
			final double z) {
		super.setPosition(x, y, z);
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void setPreferredSize(final int width, final int height) {
		preferredSize.width = width;
		preferredSize.height = height;
		if (width > 0
				&& height > 0) {
			int pWidth = width;
			if (super.getBorder() != null) {
				super.getBorder().setPreferredSize(width, height);
				pWidth -= super.getBorder().getInsets().left;
				pWidth -= super.getBorder().getInsets().right;
			}
			pWidth -= super.getInsets().left;
			pWidth -= super.getInsets().right;
			setPreferredWidth(pWidth);
			try {
				if (textGroup != null) {
					sizeText();
					positionText();
				}
			} catch (Exception ex) {
				JOGLErrorHandler.getInstance().fatalError(ex);
			}
		}
	}
	public void setPreferredTextArea(final int width, final int height) {
		int pWidth = width, pHeight = height;
		if (super.getBorder() != null) {
			pWidth += super.getBorder().getInsets().left;
			pWidth += super.getBorder().getInsets().right;
			pHeight += super.getBorder().getInsets().top;
			pHeight += super.getBorder().getInsets().bottom;
		}
		pWidth += super.getInsets().left;
		pWidth += super.getInsets().right;
		pHeight += super.getInsets().top;
		pHeight += super.getInsets().bottom;
		preferredWidth = pWidth;
		preferredHeight = pHeight;
		setPreferredSize(pWidth, pHeight);
	}
	/**
	 * Sets the preferredWidth.
	 * @param width the int to set
	 */
	public final void setPreferredWidth(final int width) {		
		preferredWidth = width;
		if (super.isDebug()) {
			System.out.println(super.getClass().getCanonicalName() 
					+ "set preferredwidth(" + width);
		}
		preferredSize.width = 0;
		preferredSize.height = 0;
		getPreferredSize();
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public final void setText(final float newText) {
		setText(Float.toString(newText));
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public final void setText(final int newText) {
		setText(Integer.toString(newText));
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public final void setText(final String newText) {
		text = newText.getBytes();
		clearLines();
		preferredSize.width = 0;
		preferredSize.height = 0;
		getPreferredSize();
	}
	/**
	 * Sizes the text for display.
	 * @throws Exception if an error occurs
	 */
	protected final void sizeText() throws Exception {
		if (text != null
				&& text.length > 0) {
			textGroup.clearLines();
			int innerWidth = preferredWidth;
			if (innerWidth == 0) {
				innerWidth = maximumWidth;
			}
			if (innerWidth == 0) {
				innerWidth = GlobalConsts.SCREEN_WIDTH;
			}
			if (super.getBorder() != null) {
				innerWidth -= super.getBorder().getInsets().left;
				innerWidth -= super.getBorder().getInsets().right;
			}
			innerWidth -= super.getInsets().left;
			innerWidth -= super.getInsets().right;
			clearLines();
			// all tabs replaced with spaces
			StringTokenizer tokenizer =
					new StringTokenizer(new String(text), " \t\n\r\f", true);
			PooledArrayList<String> words =
					StringListPool.getInstance().getList();
			words.clear();
			while (tokenizer.hasMoreTokens()) {
				String token = tokenizer.nextToken();
				words.add(token);
			}
			// start a line of text
			PooledStringBuilder sb =
					StringBuilderPool.getInstance().getStringBuilder();
			// init measurements
			double currentWidth = 0d;
			String nextWord = "";
			int nextWordLength = 0;
			FontsheetObject font =
					GameFont.getInstance().getFontById(fontRefId);
			font.setGlyph(' ');
			int spaceWidth = font.getCharWidth();
			for (int i = 0; i < words.size(); i++) {
				if (super.isDebug()) {
					System.out
							.println("word " + i + "::" + words.get(i) + "::");
				}
				if (words.get(i).charAt(0) == ' ') {
					nextWord = words.get(i);
					nextWordLength = spaceWidth;
					int newWidth = (int) currentWidth + nextWordLength;
					if (newWidth > innerWidth) {
						// end current line
						textGroup.addLine(
								innerWidth,
								super.getHorizontalAlignment(),
								sb.toString()
								);
						sb.setLength(0);
						currentWidth = 0;
						nextWord = "";
						nextWordLength = 0;
					} else {
						sb.append(nextWord);
						currentWidth = newWidth;
					}
				} else if (words.get(i).charAt(0) == '\t') { // TAB
					nextWord = GlobalConsts.TAB;
					nextWordLength = spaceWidth * GlobalConsts.TAB.length();
					int newWidth = (int) currentWidth + nextWordLength;
					if (newWidth > innerWidth) {
						// end current line
						textGroup.addLine(
								innerWidth,
								super.getHorizontalAlignment(),
								sb.toString()
								);
						sb.setLength(0);
						sb.append(nextWord);
						currentWidth = nextWordLength;
					} else {
						sb.append(nextWord);
						currentWidth = newWidth;
					}
				} else if (words.get(i).charAt(0) == '\n') { // NEWLINE
					if (sb.length() == 0) {
						sb.append(" ");
					}
					// end current line
					textGroup.addLine(
							innerWidth,
							super.getHorizontalAlignment(),
							sb.toString()
							);
					sb.setLength(0);
					currentWidth = 0;
					nextWord = "";
					nextWordLength = 0;
				} else {
					nextWord = words.get(i);
					nextWordLength = font.getTextWidth(nextWord);
					int newWidth = (int) currentWidth + nextWordLength;
					if (newWidth > innerWidth) {
						// end current line
						textGroup.addLine(
								innerWidth,
								super.getHorizontalAlignment(),
								sb.toString()
								);
						sb.setLength(0);
						sb.append(words.get(i));
						currentWidth = nextWordLength;
					} else {
						sb.append(words.get(i));
						currentWidth = newWidth;
					}
				}
			} // end for
			if (sb.length() > 0) { // add the last line
				textGroup.addLine(
						innerWidth,
						super.getHorizontalAlignment(),
						sb.toString()
						);
			}
			StringListPool.getInstance().returnObject(words);
			StringBuilderPool.getInstance().returnObject(sb);
		}
	}
	/**
	 * Tokenizes a long word String into String tokens, storing them in an array
	 * field.
	 * @param word the word
	 */
	protected final void tokenizeWords(final String word) {
		PooledArrayList<String> words =
				StringListPool.getInstance().getList();
		words.clear();
		if (word.indexOf("\n") != -1) {
			int index = word.indexOf("\n");
			if (index == 0) {
				words.add("\n");
				tokenizeWords(word.substring(1));
				for (int i = 0; i < stringArr.length; i++) {
					words.add(stringArr[i]);
				}
			} else {
				tokenizeWords(word.substring(0, index));
				// tokenize pre-linebreak
				for (int i = 0; i < stringArr.length; i++) {
					words.add(stringArr[i]);
				}
				// add linebreak
				words.add("\n");
				// tokenize post-linebreak
				tokenizeWords(word.substring(index + 1));
				for (int i = 0; i < stringArr.length; i++) {
					words.add(stringArr[i]);
				}
			}
		} else if (word.indexOf("\t") != -1) {
			int index = word.indexOf("\t");
			if (index == 0) {
				words.add("\t");
				tokenizeWords(word.substring(1));
				for (int i = 0; i < stringArr.length; i++) {
					words.add(stringArr[i]);
				}
			} else {
				tokenizeWords(word.substring(0, index));
				// tokenize pre-tab
				for (int i = 0; i < stringArr.length; i++) {
					words.add(stringArr[i]);
				}
				// add tab
				words.add("\t");
				// tokenize post-tab
				tokenizeWords(word.substring(index + 1));
				for (int i = 0; i < stringArr.length; i++) {
					words.add(stringArr[i]);
				}
			}
		} else {
			words.add(word);
		}
		stringArr = words.toArray(new String[words.size()]);
		StringListPool.getInstance().returnObject(words);
	}
}
